﻿type GameState = 
{
    Player                  : Player
    Asteroids               : Rule<Table<Asteroid>>
        ::  \(self : Gamestate) . {a | a <- self.Asteroids \and a.Enabled}
    Projectiles             : Rule<Table<Projectile>>
        ::  \(self : GameState) . {p | p <- self.Projectiles \and p.Enabled}        
    GameOver                : Rule<bool>
        ::  \(self : GameState) . (Player.Ship.Life <= 0) \or (state.Score > 100)
}

and Player =
{
    Ship                    : Ship
    Score                   : Rule<float32>
        ::  \(state : GameState) (self : Player) . 
                    self.Score
                  + length {a | a <- state.Asteroids \and a.Life <= 0}
}

and Ship =
{
    Life                    : Rule<float32>
        ::  \(self : Ship) . self.Life - (length CollidingAsteroids)
    Position                : Rule<Vector3>
        ::  \(state : GameState) (self : Ship) (dt : float32) . mantainInScreem (self.Position + (self.Velocity * dt)) state
    CollidingAsteroids      : Rule<Table<Fk<Asteroids>>>
        ::  \(state : GameState) (self : Ship) . {a | a <- state.Asteroids \and (distance a.Position self.Position) <= 1.0f \and a.IsEnabled}
    Velocity                : Ref<Vector3>
}

and Asteroid =
{
    Enabled                 : Rule<bool>
        ::  \(self : Asteroid) . (isInScreen self.Position) \and self.Life > 0 \and !IsCollidingShip
    Life                    : Rule<float32>
        ::  \(self : Asteroid) . 
            if IsCollidingShip then
                0
            else
                self.Life - (length CollidingProjectiles)
    Position                : Rule<Vector3>
        ::  \(self : Asteroid) (dt : float32) . self.Position + (self.Velocity * dt)
    Velocity                : Rule<Vector3>
        ::  \(self : Asteroid) (dt : float32) . self.Velocity + (Vector3(0, 9.8f, 0) * dt)
    CollidingProjectiles    : Rule<Table<Fk<Projectile>>>
        ::  \(state : GameState) (self : Asteroid) . {p | p <- state.Projectiles \and (distance p.Position self.Position) <= 1.0f}
    IsCollidingShip         : Rule<bool>
        ::  \(state : GameState) (self : Asteroids) . (distance state.Player.Ship.Position self.Position) <= 1.0f
}

and Projectiles =
{
    Enabled                 : Rule<bool>
        ::  \(state : GameState) (self : Projectiles) . 
            (isEmpty CollidingAsteroids) \and (isInScreen self.Position state)
    Position                : Rule<Vector3>
        ::  \(self : Projectiles) (dt : float32) . self.Position + (Velocity * dt)
    ColidingAsteroids       : Rule<Table<Fk<Asteroid>>>
        ::  \(state : GameState) (self : Projectiles) . {a | a <- state.Asteroids \and (distance a.Position self.Position) <= 1.0f}
    Velocity                : Vector3
}

State0 = 
    {
        Player      =
            {
                Ship   =
                    {
                        Life                = 10
                        Position            = Vector3.Zero
                        CollidingAsteroids  = []
                        Velocity            = Vector3.Zero

                    }
                Score  = 0
            }
        Asteroids   = []
        Projectiles = []
        GameOver    = false
    }
Script0 =
    
    let rec wait (interval:float) : Unit =
        script{
            let! t0 = time
            do! yield
            let! t = time
            let dt = t - t0
            let interval' = interval - dt
            if interval' > 0 then 
                do! wait interval'
            else
                return ()
        }

    let newAsteroid (state:GameState) : Unit =
        script{
            let! pos = randomPosition
            do! state.Asteroids.Add 
              {
                 Enabled = true
                 Life = 5
                 Position = pos
                 Velocity = Vector3 (0,0,0)
                 CollidingProjectiles = []
                 IsCollidingShip = false
              }
        }

    let spawnAsteroid (state:GameState) : Unit =
        script{
            let! upBeforeNew = randomNumber(0.5,2.5)
            do! wait randomNumber
            do! newAsteroid state
        }

    let checkDefeat (state:GameState) : Option<bool> =
        script{
            if state.GameOver then
              if state.Score > 100 then
                return Some false
              else
                return Some true
            else
              return None
        }

    let GameLogic (state:GameState) : Option<bool> = 
        script{
            let! Left(res) =
                       checkVictory state =>
                         fun defeated ->
                             script{
                                return defeated
                             }
                        || (repeat (spawnAsteroid state))
            return res
        }

    let InputManager (state:GameState) : Option<bool> =
        script{
            let! pressedKeys = Keyboard.Pressed
            for key in pressedKeys do
                match key with
                | Keys.Left  -> do! state.Player.Ship.Velocity := Vector3.Left
                | Keys.Right -> do! state.Player.Ship.Velocity := Vector3.Right
                | Keys.Space -> do! state.Projectiles.Add
                    {
                        Enabled             = true
                        Position            = state.Player.Ship.Position
                        ColidingAsteroids   = []
                        Velocity            = Vector3.Up * 5
                    }
            if pressedKeys |> Seq.exists ((=) Keys.Escape) then
                do! state.GameOver := true
                return Some false
            else
                return None
        }

    let MainScript (state:GameState) : Option<bool> =
        script{
            let! res = 
                InputManager state =>
                    fun _ ->
                        script{
                            return false
                        }
                || (GameLogic state)
            match res with
            | Left(status) | Right(status) -> return status
        }
    in
    MainScript



script {
    do! yield
    let! (x:a) = s // s:Script<a>
    retyrn y
}

repeat (s:script<()>):script<()>

let! (x,y) = s1 && s2   //parallel tra s1 e s2; x = s1 y = s2

let! res = s1 || s2     //concurrent
match res with
|Left x -> ...      // ha finito prima s1 con risultato x
|Right y -> ...     // ha finito prima s2 con risultato y
 
(c => s) : script<b>    // While c ritorna none eseguilo, poi s
c: script<Option<a>>
s: a->script<b>

(run (s:script<()>):script<()>) aggiunge uno script al motore di scripting


1. tipi di ritorno degli scripts        OK
2. identifica gli elementi della grammatica con un esempio nel sample di ogni elemento della grammatica nel paper
3. implementare questo gioco in xna
            